﻿using Nes.Core.Buss;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nes.Core.APUS
{
    public class Dmc
    {
        public IBus CPUBus { get; set; }
        public ICPU CPU { get; set; }
        public bool InterruptFlag { get; internal set; }

        private Boolean IRQEnable;
        private Boolean IsLoopEnable;
        private Int32 frequency;
        private Int32 clock;
        private Int32 loadConter;
        private Int32 sampleAddress;
        private Int32 sampleLength;
        private Int32 addressCounter;
        public Int32 BytesRemainingCounter { get; internal set; }
        private Boolean isMuted;
        private Int32 sampleBuffer;
        private Int32 BitsRemainingCounter;

        public Dmc()
        {
            isMuted = true;
        }

        public Boolean Enable { get; set; }
        public Int32 Volume { get; internal set; }

        internal void Clock()
        {
            if (!Enable)
            {
                return;
            }
            if(clock % (CommonProp.DMCTable[frequency] + 1) == 0)
            {
                OutputUnit();
            }
            clock++;
        }

        private void OutputUnit()
        {
            if(BitsRemainingCounter <= 0)
            {
                if (isMuted)
                {
                    return;
                }
                MemoryReader();
                BitsRemainingCounter = 8;
            }
            if((sampleBuffer & 0x01) > 0)
            {
                Volume = Volume > 125 ? 127 : Volume + 2;
            }
            else
            {
                Volume = Volume < 2 ? 0 : Volume - 2;
            }
            sampleBuffer >>= 1;
            BitsRemainingCounter--;
        }

        private void MemoryReader()
        {
            if(BytesRemainingCounter <= 0 || BitsRemainingCounter > 0)
            {
                return;
            }
            sampleBuffer = CPUBus.ReadByte(addressCounter);
            addressCounter = addressCounter >= 0xFFFF ? 0x8000 : addressCounter + 1;
            BytesRemainingCounter--;
            if(BytesRemainingCounter <= 0)
            {
                if (IsLoopEnable)
                {
                    RestartSample();
                }
                else
                {
                    isMuted = true;
                    if (IRQEnable)
                    {
                        InterruptFlag = true;
                        CPU.IRQ();
                    }
                }
            }

        }

        internal void Write(Int32 addr, Byte data)
        {
            switch (addr)
            {
                case 0: // IL--.RRRR
                    IRQEnable = (data & 0b1000_0000) > 0;
                    IsLoopEnable = (data & 0b0100_0000) > 0;
                    frequency = data & 0b0000_1111;
                    clock = 0;
                    if (!IRQEnable)
                    {
                        InterruptFlag = false;
                    }
                    break;
                case 1: // -DDD.DDDD	Direct load (write)
                    loadConter = data & 0b0111_1111;
                    RestartSample();
                    break;
                case 2: // AAAA.AAAA	Sample address (write)
                    //sampleAddress = data * 64 + 0xC000;
                    sampleAddress = (data << 6) + 0xC000;
                    RestartSample();
                    break;
                case 3: // LLLL LTTT	Length counter load (L), timer high (T), set linear counter reload flag
                    //sampleLength = data * 16 + 1;
                    sampleLength = (data << 4) + 1;
                    RestartSample();
                    break;
            }
        }

        private void RestartSample()
        {
            addressCounter = sampleAddress;
            BytesRemainingCounter = sampleLength;
            isMuted  = false;
            Volume = loadConter;
        }
    }
}
